package ch.epfl.lara.pm.ast

import scala.util.parsing.input.Positional

sealed abstract class Tree extends Positional
  
final case class Program(val constraints: List[(String,Formula)], val objs: List[ObjectDef], val cls: List[ClassDef], val expr: Expr) extends Tree 

final case class ObjectDef(val name: String, val extractor: ExtractorDef) extends Tree


final case class ExtractorDef(val arg: ParamDef, val tpe: TypeTree, val body: Expr) extends Tree {
  def getParamtype: TypeTree = arg.tpe
}
  

final case class ClassDef(val isAbstract: Boolean, val name: String, val fields: List[ParamDef], val superc: String, val methods: List[MethodDef]) extends Tree {
  import ch.epfl.lara.pm.global.ClassTable._
  
  
  /**
   * Ordered list of superclasses of the current instance 
   */
  def superclasses: List[String] = {
    if(isRoot) Nil
    else superc :: lookupClass(superc).superclasses
  }
  
  
  /**
   * Determine if the current instance is a subclass of the class gave in argument of the function.
   */
  def isSubtype(that: ClassDef): Boolean = { 
    isSameType(that) ||
    superc == that.name || 
    (if (isRoot) false else lookupClass(superc).isSubtype(that))
  }
  
  /**
   * Determine if the current instance is the same class as the one gave in argument of the function.
   */
  def isSameType(that: ClassDef): Boolean = 
    name == that.name
    
  /**
   * Least upper bound (also called supremum)
   */
  def lub(that: ClassDef): Option[String] = {
    if(isSubtype(that)) Some(that.name)
    else if(that.isSubtype(this)) Some(name)
    else superclasses.intersect(that.superclasses) match {
      case Nil => None
      case head :: tail => Some(head)
    } 
  }
  
  
  def isRoot: Boolean = superc == root 
  
  /**
   * Return the top hierarchy class of the current instance that is not the predefined top-type "Any"
   */
  def getRoot: ClassDef = {
    if(isRoot)
      this
    else
      lookupClass(superc) 
  }
  
  def getRootName = getRoot.name
  
}


final case class ParamDef(name: String, tpe: TypeTree) extends Tree

  
final case class MethodDef(name: String, params: List[ParamDef], tpe: TypeTree, body: Expr) extends Tree

  
final case class CaseDef(val position: Int, val pattern: PatternDef, val guard: Option[Expr], body: Expr) extends Tree with PatternMatching


sealed abstract class PatternDef extends Tree with PatternMatching {
  
  def getTpe: String
  
  /**
   * Get the list of extractors triggered in the pattern expression.
   */
  def getExtractors: List[(String,ExtractorDef)] = Nil
}
  
final case class VarPattern(val name: PatternName, val tpe: TypeTree) extends PatternDef{
  override def getTpe = tpe.toString
}


  
final case class ClassPattern(val objname: PatternName, val args: List[PatternDef]) extends PatternDef{
  override def getTpe = objname.name 
  
  import ch.epfl.lara.pm.global.ObjectTable
  override def getExtractors: List[(String,ExtractorDef)]  = 
    (objname.name,ObjectTable.getExtractor(objname.name)) :: args.flatMap(arg => arg.getExtractors) 
}


  
sealed abstract class Expr extends Tree
  
final case class Var(name: String) extends Expr

final case object True extends Expr
  
final case object False extends Expr
  
final case class Call(obj: Expr, method: String, args: List[Expr]) extends Expr
  
final case class Select(obj: Expr, field: String) extends Expr
  
final case class If(cond: Expr, then: Expr, elze: Expr) extends Expr
  
final case class New(name: String, args: List[Expr]) extends Expr

final case object INone extends Expr 

final case class IsInstanceOf(e: Expr, tpe: String) extends Expr
  
trait PatternMatching 

final case class Match(val scrutinee: Expr, val tpe: TypeTree, val cases: List[CaseDef]) extends Expr with PatternMatching {
  
  /**
   * Retrieve the list of pattern declaration of the current match expression
   */
  def getPatterns: List[PatternDef] = 
    for(val CaseDef(_,pattern,_,_) <- cases) yield pattern
    
 
  def getTriggeredRootExtractors: List[String] = 
    (for(pattern<-getPatterns if pattern.isInstanceOf[ClassPattern]) yield {
      pattern.asInstanceOf[ClassPattern].objname.name  
    }).removeDuplicates
  
    
}
  
  
final case class Number(n: Int) extends Expr 
  
final case class Plus(left: Expr, right: Expr) extends Expr 
  
final case class Minus(left: Expr, right: Expr) extends Expr
  
final case class Prod(left: Expr, right: Expr) extends Expr 
  
final case class Div(left: Expr, right: Expr) extends Expr
  
final case class Mod(left: Expr, right: Expr) extends Expr
  
  
final case class Eq(left: Expr, right: Expr) extends Expr
  
final case class Less(left: Expr, right: Expr) extends Expr
  
final case class LessEq(left: Expr, right: Expr) extends Expr
  
final case class EAnd(left: Expr, right: Expr) extends Expr
  
final case class EOr(left: Expr, right: Expr) extends Expr

// Internally used
final case class EImply(left: Expr, right: Expr) extends Expr

final case class Not(e: Expr) extends Expr

final case class NotEq(left: Expr, right: Expr) extends Expr
  

  

sealed abstract class TypeTree extends Tree {
  def isPredefinedType = true
}

final case class ClassType(c: String) extends TypeTree {
  override def isPredefinedType = false
  override def toString = c
}

final case class OptionType(seq: List[TypeTree]) extends TypeTree {
  override def toString = {
    "Option[" + (
        if (seq.length > 1) seq.mkString("(",",",")")
        else seq.head
                ) + "]"
  }
}

final case object IntType extends TypeTree {
  override def toString = "Integer"
}

final case object BoolType extends TypeTree {
  override def toString = "Boolean"
}

final case object WrongType extends TypeTree


